#define ZCORE_SOURCE
#include <EnvDetect.hpp>
#include "FileTools.hpp"
#include "Log.hpp"
#include <sys/stat.h>
#ifdef ZZZ_OS_WIN
  #include <3rdParty/dirent.h>
  #include <direct.h>
  #define _GetCurrentDir _getcwd
  #define _MkDir _mkdir
  #define SPLITOR "\\"
#else
  #include <dirent.h>
  #include <unistd.h>
  #define _GetCurrentDir getcwd
  #define _MkDir mkdir
  #define SPLITOR "/"
#endif

namespace zzz{
// This should be called on program initialization, before main
string INITIAL_PATH = CompleteDirName(CurrentPath());

#ifdef ZZZ_LIB_BOOST
// Boost Implementation
///////////////////////////////////////////////
//path
Path RelativeTo(const Path &src, const Path &dest) 
{
  ZCHECK(dest.is_complete() && src.is_complete());
  Path srcpath=src.has_filename()?src.parent_path():src;

  Path::iterator dest_begin = dest.begin(), dest_end = dest.end();
  Path::iterator src_begin = srcpath.begin(), src_end = srcpath.end();
  Path result;

#if defined(BOOST_WINDOWS)
  //#if defined(WIN32)
  // paths are on different drives (like, "c:/test/t.txt" and "d:/home")
  if (dest.root_name() != srcpath.root_name()) return dest;
  if (src_begin != src_end) ++src_begin;
  if (dest_begin != dest_end) ++dest_begin;
#endif

  // ignore directories that are same
  while ((src_begin != src_end) && (dest_begin != dest_end)) {
    if (*src_begin != *dest_begin) break;
    ++src_begin, ++dest_begin;
  }

  // now, we begin to relativize
  while (src_begin != src_end) {
    result /= "..";
    ++src_begin;
  }

  while (dest_begin != dest_end) {
    result /= *dest_begin;
    ++dest_begin;
  }
  return result;
} 

string GetExt(const string &str)
{
  Path p(str);
  return p.extension();
}

string GetBase(const string &str)
{
  Path p(str);
  return p.stem();
}

string GetFilename(const string &str)
{
  Path p(str);
  return p.filename();
}

string GetPath(const string &str)
{
  Path p(str);
  string parent_path = p.parent_path().file_string();
  if (!parent_path.empty())
    parent_path += '\\';
  else
    parent_path = ".\\";
  return parent_path;
}

vector<string> SplitPath(const string &path)
{
  Path p(path);
  vector<string> splits(p.begin(), p.end());
  return splits;
}

string PathFile(const string &path,const string &file)
{
  Path p(path);
  Path f(file);
  p.remove_filename();
  if (!f.has_root_path()) p/=f;
  else p=f;
  return p.file_string();
}

void NormalizePath(string &path)
{
  Path p(path);
  p.normalize();
  path=p.file_string();
}

string RelativeTo(const string &a,const string &to_a)
{
  return RelativeTo(Path(a),Path(to_a)).file_string();
}

bool FileExists(const string &filename)
{
  Path p(filename);
  return boost::filesystem2::exists(p) && boost::filesystem2::is_regular_file(p);
}

bool DirExists(const string &filename)
{
  Path p(filename);
  return boost::filesystem2::exists(p) && boost::filesystem2::is_directory(p);
}

bool IsSymlink(const string &filename)
{
  Path p(filename);
  return boost::filesystem2::exists(p) && boost::filesystem2::is_symlink(p);
}

void ListFileOnly(const string &path, bool recursive, vector<string> &files)
{
  Path dir_path(path);
  if (!exists(dir_path)) return;
  boost::filesystem::directory_iterator end_itr; // default construction yields past-the-end
  for (boost::filesystem::directory_iterator itr(dir_path); itr != end_itr; ++itr) {
    if (boost::filesystem2::is_directory(itr->status())) {
      if (recursive)
        ListFileOnly(itr->path().string(), recursive, files);
    } else {
      files.push_back(itr->path().string());
    }
  }
}

void ListDirOnly(const string &path, bool recursive, vector<string> &files)
{
  Path dir_path(path);
  if (!exists(dir_path)) return;
  boost::filesystem::directory_iterator end_itr; // default construction yields past-the-end
  for (boost::filesystem::directory_iterator itr(dir_path); itr != end_itr; ++itr) {
    if (boost::filesystem2::is_directory(itr->status())) {
      files.push_back(CompleteDirName(itr->path().string()));
      if (recursive)
        ListDirOnly(itr->path().string(), recursive, files);
    }
  }
}

void ListFileAndDir(const string &path, bool recursive, vector<string> &files)
{
  Path dir_path(path);
  if (!exists(dir_path)) return;
  boost::filesystem::directory_iterator end_itr; // default construction yields past-the-end
  for (boost::filesystem::directory_iterator itr(dir_path); itr != end_itr; ++itr)
  {
    if (boost::filesystem2::is_directory(itr->status()) && recursive) {
      files.push_back(CompleteDirName(itr->path().string()));
      ListFileAndDir(itr->path().string(), recursive, files);
    }
    else
      files.push_back(itr->path().string());
  }
}

bool PathEquals(const string &f1, const string &f2)
{
  Path p1(f1),p2(f2);
  return p1==p2;
}

string CurrentPath()
{
  return boost::filesystem::current_path().file_string()+"\\";
}

bool CopyFile(const string &from, const string &to)
{
  if (!FileCanOpen(from) || FileCanOpen(to)) return false;
  boost::filesystem::copy_file(Path(from),Path(to));
  return true;
}

bool RenameFile(const string &from, const string &to)
{
  if (!FileCanOpen(from) || FileCanOpen(to)) return false;
  boost::filesystem::rename(Path(from),Path(to));
  return true;
}

bool RemoveFile(const string &f, bool recursive)
{
  Path p(f);
  if (!boost::filesystem::exists(p)) return false;
  if (!recursive && boost::filesystem::is_directory(p) && !boost::filesystem::is_empty(p)) return false;
  if (recursive)
    return boost::filesystem::remove_all(p)>0;
  else
    return boost::filesystem::remove(p);
}

bool MakeDir(const string &dir)
{
  return boost::filesystem::create_directories(Path(dir));
}

bool MakeHardLink(const string &from, const string &to)
{
  if (!FileCanOpen(from) || FileCanOpen(to)) return false;
  boost::filesystem::create_hard_link(Path(from),Path(to));
  return true;
}

bool MakeSymLink(const string &from, const string &to)
{
  if (!FileCanOpen(from) || FileCanOpen(to)) return false;
  boost::filesystem::create_symlink(Path(from),Path(to));
  return true;
}
#else
///////////////////////////////////////////////////////////////////////
// Native Implementation
string GetExt(const string &str)
{
  string::size_type dot_pos = str.rfind('.');
  string::size_type slash_pos = str.rfind('/');
  if (slash_pos == string::npos)
    slash_pos = str.rfind('\\');
  if (dot_pos == string::npos)
    return string(".");
  if (slash_pos == string::npos) {
    return str.substr(dot_pos, str.size() - dot_pos);
  } else if (slash_pos < dot_pos) {
      return str.substr(dot_pos, str.size() - dot_pos);
  } else {
    return string(".");
  }
}

string GetBase(const string &str)
{
  string::size_type dot_pos = str.rfind('.');
  string::size_type slash_pos = str.rfind('/');
  if (slash_pos == string::npos)
    slash_pos = str.rfind('\\');

  if (dot_pos == string::npos)
    dot_pos = str.size();

  if (slash_pos == string::npos) {
    return str.substr(0, dot_pos);
  } else if (slash_pos < dot_pos) {
    return str.substr(slash_pos + 1, dot_pos - slash_pos - 1);
  } else {
    return string();
  }
}

string GetFilename(const string &str)
{
  string::size_type slash_pos = str.rfind('/');
  if (slash_pos == string::npos)
    slash_pos = str.rfind('\\');
  if (slash_pos == string::npos) {
    return str;
  } else {
    return str.substr(slash_pos + 1, str.size() - slash_pos - 1);
  }
}

string GetPath(const string &str)
{
  string::size_type slash_pos = str.rfind('/');
  if (slash_pos == string::npos)
    slash_pos = str.rfind('\\');
  if (slash_pos == string::npos) {
    return CompleteDirName(".");
  } else {
    return str.substr(0, slash_pos+1);
  }
}

vector<string> SplitPath(const string &path)
{
  vector<string> splits(1);
  for (string::const_iterator si = path.begin(); si != path.end(); si++) {
    if (*si == '/' || *si == '\\') {
      if (splits.size() == 1 && splits[0][1] == ':')
        splits.push_back("/");
      splits.push_back(string());
    } else {
      splits.back().push_back(*si);
    }
  }
  if (splits.back().empty())
    splits.back() = ".";
  return splits;
}

bool HasRoot(const string &str)
{
#ifdef ZZZ_OS_WIN
  if (isalpha(str[0]) && str[1] == ':')
    return true;
#else
  if (str[0] == '/')
    return true;
#endif
  return false;
}

string PathFile(const string &path,const string &file)
{
  if (!HasRoot(file))
    return GetPath(path) + file;
  else
    return file;
}

bool FileExists(const string &filename)
{
  struct stat stFileInfo;
  return stat(filename.c_str(), &stFileInfo) == 0 && (stFileInfo.st_mode & S_IFMT) == S_IFREG;
}

bool DirExists(const string &filename)
{
  struct stat stFileInfo;
  return stat(filename.c_str(), &stFileInfo) == 0 && (stFileInfo.st_mode & S_IFMT) == S_IFDIR;
}

void ListFileOnly(const string &path, bool recursive, vector<string> &files)
{
  DIR *dp;
  dirent *dirp;
  if((dp  = opendir(path.c_str())) == NULL) {
    ZLOGE << "Error(" << errno << ") opening " << path << endl;
  }
  while ((dirp = readdir(dp)) != NULL) {
    if (dirp->d_type == DT_DIR) {
      if (recursive)
        ListFileOnly(dirp->d_name, recursive, files);
    }
    else
      files.push_back(string(dirp->d_name));
  }
  closedir(dp);
}

void ListDirOnly(const string &path, bool recursive, vector<string> &files)
{
  DIR *dp;
  dirent *dirp;
  if((dp  = opendir(path.c_str())) == NULL) {
    ZLOGE << "Error(" << errno << ") opening " << path << endl;
  }
  while ((dirp = readdir(dp)) != NULL) {
    if (dirp->d_type == DT_DIR) {
      files.push_back(string(dirp->d_name));
      if (recursive)
        ListFileOnly(dirp->d_name, recursive, files);
    }
  }
  closedir(dp);
}

void ListFileAndDir(const string &path, bool recursive, vector<string> &files)
{
  DIR *dp;
  dirent *dirp;
  if((dp  = opendir(path.c_str())) == NULL) {
    ZLOGE << "Error(" << errno << ") opening " << path << endl;
  }
  while ((dirp = readdir(dp)) != NULL) {
    files.push_back(string(dirp->d_name));
    if (dirp->d_type == DT_DIR) {
      if (recursive)
        ListFileOnly(dirp->d_name, recursive, files);
    }
  }
  closedir(dp);
}

string CurrentPath()
{
  char cCurrentPath[FILENAME_MAX];
  _GetCurrentDir(cCurrentPath, sizeof(cCurrentPath));
  return CompleteDirName(cCurrentPath);
}

bool CopyFile(const string &from, const string &to)
{
  ifstream f1(from, fstream::binary);
  ofstream f2(to, fstream::trunc|fstream::binary);
  f2 << f1.rdbuf();
  f1.close();
  f2.close();
  if (!FileCanOpen(from) || FileCanOpen(to)) return false;
  return true;
}

bool RenameFile(const string &from, const string &to)
{
  return rename(from.c_str(), to.c_str()) == 0;
}

bool RemoveFile(const string &f, bool recursive)
{
  return remove(f.c_str()) == 0;
}

bool MakeDir(const string &dir)
{
  return _MkDir(dir.c_str()) == 0;
}

#endif // ZZZ_LIB_BOOST

string InitialPath()
{
  return INITIAL_PATH;
}

std::string CompleteDirName(const string &str)
{
  if (str.back()=='/' || str.back()=='\\')
    return str;
  else
    return str + SPLITOR;
}


//////////////////////////////////////////
//file
bool ReadFileToString(const string &filename,char **buf)
{
  unsigned long len=GetFileSize(filename);

  FILE *fp=fopen(filename.c_str(), "rb");
  if (fp==NULL) {
    ZLOGE<<"Cannot open file: "<<filename<<endl;
    return false;
  }
  *buf=new char[len+1];
  memset(*buf, 0,sizeof(char)*(len+1)); //VERY IMPORTANT, otherwise fread may read out wrong data
  ZCHECK(fread(*buf,len, 1,fp)==1);
  fclose(fp);
  return true;
}

bool ReadFileToString(const string &filename,string &buf)
{
  char *charbuf;
  ReadFileToString(filename, &charbuf);
  buf=charbuf;
  delete[] charbuf;
  return true;
}

bool SaveStringToFile(const string &filename,string &buf)
{
  FILE *fp=fopen(filename.c_str(), "wb");
  if (fp==NULL) {
    printf("Cannot open file: %s\n",filename);
    return false;
  }
  fwrite(buf.c_str(),buf.size(), 1,fp);
  fclose(fp);
  return true;
}

unsigned long GetFileSize(const string &filename)
{
  FILE *pFile = fopen(filename.c_str(), "rb");
  if (pFile==NULL) return 0;
  fseek(pFile, 0, SEEK_END);
  unsigned long size = ftell(pFile);
  fclose(pFile);
  return size;
}

bool FileCanOpen(const string &filename)
{
  FILE *fp=fopen(filename.c_str(), "rb");
  if(fp!=NULL) {
    fclose(fp);
    return true;
  }
  return false;
}

string RemoveComments_copy(const string &buf)
{
  string buf2;
  bool incomment1=false,incomment2=false;
  int len=buf.size();
  for(int i=0; i<len; i++) {
    char x=buf[i];
    if (incomment1) {
      if (x=='\n') {
        incomment1=false;
        buf2.push_back(x);
      }
      continue;
    }
    if (incomment2) {
      if (x=='*' && i+1!=len && buf[i+1]=='/') {
        incomment2=false;
        i++;
        continue;
      }
      if (x=='\n')
        buf2.push_back(x);
      continue;
    }
    if (x=='/') {
      if (i+1!=len && buf[i+1]=='/') {
        incomment1=true;
        i++;
        continue;
      }
      if (i+1!=len && buf[i+1]=='*') {
        incomment2=true;
        i++;
        continue;
      }
    }
    if (x=='\\' && buf[i+1]=='\n') {
      i++;
      continue;
    }
    if (x=='; ') x='\n';
    buf2.push_back(x);
  }
  return buf2;
}

bool RemoveComments(string &buf)
{
  string buf2=RemoveComments_copy(buf);
  buf=buf2;
  return true;
}

zzz::zuint FileCountLine(ifstream &fi)
{
  streampos oripos=fi.tellg();
  int line=0;
  string tmp;
  while(true) {
    getline(fi,tmp, '\n');
    if (fi.fail())
      break;
    line++;
  }
  fi.clear();
  fi.seekg(oripos);
  return line;
}

} // namespace zzz